package com.sun.common.core.utils;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;

import com.sun.common.core.excel.model.*;
import com.sun.common.core.model.dto.SimpleVO;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Validation;
import javax.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

/**
 * The type Excel util.
 */
@SuppressWarnings(SuppressWarningConstant.UNUSED)
public class ExcelUtil<T> {
    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);

    public static final Validator VALIDATOR = Validation.byProvider(HibernateValidator.class)
            .configure()
            .failFast(false)
            .allowMultipleCascadedValidationOnReturnValues(true)
            .buildValidatorFactory()
            .getValidator();

    private ExcelUtil() {

    }

    /**
     * Read excel read result.
     *
     * @param <T>      the type parameter
     * @param meta     the meta {@link ReadMeta <T>}
     * @param listener the listener {@link AnalysisEventListener}
     * @return the read result
     */
    public static <T extends Serializable> ReadResult readExcel(final ReadMeta<T> meta, final AnalysisEventListener<T> listener) {
        ExcelReader reader = null;
        final ReadResult result = new ReadResult(Boolean.TRUE);
        try {
            reader = EasyExcelFactory.read(meta.getExcelStream(), meta.getClazz(), listener)
                    .headRowNumber(meta.getHeadRowNumber())
                    .password(meta.getPassword())
                    .ignoreEmptyRow(Boolean.TRUE)
                    .autoCloseStream(Boolean.TRUE)
                    .build();
            reader.readAll();
        } catch (final ExcelDataConvertException ex) {
            result.setSuccess(Boolean.FALSE);
            result.setFailMessage(ex.getRowIndex() + meta.getHeadRowNumber(), ex.getColumnIndex() + 1, ex.getMessage());
            log.error(result.getMessage(), ex.getCause());
        } finally {
            Optional.ofNullable(reader).ifPresent(ExcelReader::finish);
        }
        return result;
    }

    public static <T extends Serializable> SimpleVO readExcel(InputStream inputStream, Class<T> clazz, final AnalysisEventListener<T> listener) {
        ExcelReader reader = null;
        final SimpleVO result = new SimpleVO<>(Boolean.TRUE);
        try {
            reader = EasyExcelFactory.read(inputStream, clazz, listener)
                    .headRowNumber(1)
                    .excelType(ExcelTypeEnum.XLSX)
                    .ignoreEmptyRow(Boolean.TRUE)
                    .autoCloseStream(Boolean.TRUE)
                    .build();

            ReadSheet sheet = new ReadSheet();
            sheet.setSheetName("Sheet1");
            sheet.setSheetNo(1);

            reader.read(sheet);
        } catch (final ExcelDataConvertException ex) {
            result.setSuccess(Boolean.FALSE);
            result.setMsg(ex.getMessage());
            log.error(result.getMsg(), ex.getCause());
        } finally {
            Optional.ofNullable(reader).ifPresent(ExcelReader::finish);
        }
        return result;
    }


    /**
     * Write excel.
     *
     * @param <T>  the type parameter
     * @param meta the meta
     * @param data the data
     */
    public static <T> void writeExcel(final WriteMeta<T> meta, final List<T> data) {
        writeExcel(meta.getExcelStream(), data, meta.getClazz(), meta.getType());
    }

    /**
     * Write excel.
     *
     * @param <T>          the type parameter
     * @param outputStream the output stream
     * @param list         the list
     * @param clazz        the clazz
     * @param type         the type
     */
    public static <T> void writeExcel(final OutputStream outputStream, final List<T> list, final Class<T> clazz, final ExcelTypeEnum type) {
        ExcelWriter writer = null;
        final int length = multiple(list.size(), BasicConstant.EXCEL_MAX_ROW);
        try {
            final ExcelWriterBuilder write = EasyExcelFactory.write(outputStream)
                    .excelType(type);
            writer = write.build();
            for (int sheetNo = 0; sheetNo < length; sheetNo++) {
                final WriteSheet sheet = EasyExcelFactory.writerSheet(sheetNo, String.format("sheet%s", sheetNo)).head(clazz).build();
                writer.write(ComUtils.page(sheetNo + 1, BasicConstant.EXCEL_MAX_ROW, list), sheet);
            }
        } finally {
            Optional.ofNullable(writer).ifPresent(ExcelWriter::finish);
        }
    }

    /**
     * Write http servlet excel.
     *
     * @param <T>       the type parameter
     * @param response  the response
     * @param writeMeta the writer meta
     * @throws IOException the io exception
     */
    public static <T> void writeHttpServletExcel(final HttpServletResponse response, final WriteMeta<T> writeMeta) throws IOException {
        response.setContentType(ExcelTypeEnum.XLS.equals(writeMeta.getType()) ? HttpMediaType.APPLICATION_EXCEL_XLS_VALUE : HttpMediaType.APPLICATION_EXCEL_XLSX_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(writeMeta.getFilename(), StandardCharsets.UTF_8.name()) + writeMeta.getType().getValue());
        writeExcel(response.getOutputStream(), writeMeta.getData(), writeMeta.getClazz(), writeMeta.getType());
    }


    /**
     * 计算num1,num2的倍数
     *
     * @param num1 the num 1
     * @param num2 the num 2
     * @return 倍数 int
     */
    public static int multiple(int num1, int num2) {
        if (num1 == 0 || num2 == 0) {
            return 0;
        }
        if (num1 < num2) {
            return 1;
        }
        if (num1 % num2 == 0) {
            return num1 / num2;
        }
        return num1 / num2 + 1;
    }


}
